package com.xiaojie.lock.impl;

import com.xiaojie.lock.RedisLock;
import com.xiaojie.lock.RedisLockInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author xiaojie
 * @version 1.0
 * @description: 实现分布式锁的实现类
 * @date 2022/6/9 18:05
 */
@Component
@Slf4j
public class RedisLockImpl implements RedisLock {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private String redisLockKey = "redisLockKey";
    /**
     * 缓存redis锁
     */
    private static Map<Thread, RedisLockInfo> lockCacheMap = new ConcurrentHashMap<>();
    /**
     * 重试时间
     */
    private Long timeout = 3000L;
    private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();


    @Override
    public boolean tryLock() {
        Thread currentThread = Thread.currentThread();
        RedisLockInfo redisLockInfo = lockCacheMap.get(currentThread);
        //判断是不是当前线程获取到了锁
        if (null != redisLockInfo && redisLockInfo.isState()) {
            //证明已经获取到了锁,锁重入
            log.info(">>>>>>>>>>>>>>>>>已经获取到了锁");
            redisLockInfo.incrLockCount(); //获取锁的次数加1
            return true;
        }
        Long startTime = System.currentTimeMillis();
        Long lockExpire = 30l; //键值的过期时间
        //重试获取锁
        for (; ; ) {
            Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(redisLockKey, "1", lockExpire, TimeUnit.SECONDS);
            if (lock) {
                //获取锁成功
                log.info(">>>>>>>>>>>>>>获取锁成功");
                lockCacheMap.put(currentThread, new RedisLockInfo(currentThread, lockExpire));
                return true;
            }
            // 控制一个超时的时间
            Long endTime = System.currentTimeMillis();
            if (endTime - startTime > timeout) {
                log.info("在3秒内已经过了重试时间了>>>>>>>>>>>");
                return false;
            }
            // 继续循环
            try {
                Thread.sleep(10); //休眠避免CPU飙高
            } catch (Exception e) {

            }
        }
    }

    @Override
    public boolean releaseLock() {
        Thread currentThread = Thread.currentThread();
        RedisLockInfo redisLockInfo = lockCacheMap.get(currentThread);
        if (null != redisLockInfo && redisLockInfo.isState()) {
            if (redisLockInfo.decreLockCount() <= 0) {
                lockCacheMap.remove(currentThread);
                //删除键值
                stringRedisTemplate.delete(redisLockKey);
                return true;
            }
        }
        return false;
    }

    public RedisLockImpl() {
        //开始定时任务实现续命,每3秒续命一次
        this.scheduledExecutorService.scheduleAtFixedRate(new LifeExtensionThread(), 0, 30*60, TimeUnit.SECONDS);
    }

    //定义监听线程
    class LifeExtensionThread implements Runnable {
        @Override
        public void run() {
            log.info("开始续命线程>>>>>>>>>>>>>>>>>>>>>>>>>");
            if (lockCacheMap.size() > 0) {
                lockCacheMap.forEach((k, lockInfo) -> {
                    try {
                        Thread lockServiceThread = lockInfo.getLockThread();
                        if (lockServiceThread.isInterrupted()) {
                            //判断线程是否执行完毕
                            log.info("当前线程已经被停止>>>>>>>>>>>>>>>");
                            lockCacheMap.remove(k); //从缓存中移除线程
                            return;
                        }
                        // 续命键值过期时间
                        Integer lifeCount = lockInfo.getLifeCount();
                        if (lifeCount > 5) {
                            log.info(">>>>>>>>>>>>>>>>>您已经续命了多次当前线程还没有释放锁，现在主动将该锁释放 避免死锁的问题");
                            // 1.回滚业务（例如事务回滚）
                            // 2.释放锁
                            releaseLock();
                            // 3.将该线程主动停止
                            lockServiceThread.interrupt();
                            // 4.移除监听
                            lockCacheMap.remove(k);
                            return;
                        }
                        //提前实现续命 延长过期key的时间
                        stringRedisTemplate.expire(redisLockKey, lockInfo.getExpire(), TimeUnit.SECONDS);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }
}
